package com.ctgu.redislock02.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ctgu.redislock02.entity.ProductsEntity;
import com.ctgu.redislock02.mapper.ProductsMapper;
import com.ctgu.redislock02.rabbitmq.RabbitMQConfig;
import com.ctgu.redislock02.service.ProductsService;
import com.ctgu.redislock02.utils.ApiResult;
import lombok.extern.log4j.Log4j2;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
* @author Alex2
* @description 针对表【t_products】的数据库操作Service实现
* @createDate 2025-03-13 16:10:32
*/
@Log4j2
@Service
public class ProductsServiceImpl extends ServiceImpl<ProductsMapper, ProductsEntity>
    implements ProductsService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private static final String TAG = "ALEX";
    private static final String REDIS_KEY_LOCK_PRODUCTS = "lock:product:";
    private static final String REDIS_KEY_STOCK_PRODUCTS = "stock:product:";
    private static final String QUEUE_NAME = "product_order_queue";

    /**
     * @Author: Alex
     * @Description: 插入商品
     */
    @Override
    public ApiResult insertProduct(Integer id, String name, Integer stack) {
        Boolean exist = redisTemplate.hasKey(REDIS_KEY_STOCK_PRODUCTS + id);
        if (Boolean.FALSE.equals(exist)) {
            ProductsEntity productsEntity = new ProductsEntity(null, name, stack);
            boolean save = save(productsEntity);
            redisTemplate.opsForValue().set(REDIS_KEY_STOCK_PRODUCTS + id, stack.toString());
            if (!save) {
                return ApiResult.error("插入失败");
            } else {
                return ApiResult.success("插入成功");
            }
        } else {
            return ApiResult.error("该商品已存在，插入失败");
        }
    }

    /**
     * @Author: Alex
     * @Description: SET NX 互斥锁
     */
    @Override
    public ApiResult getProductWithLock(Integer id, Integer userId) {
        Boolean success = redisTemplate.opsForValue().setIfAbsent(REDIS_KEY_LOCK_PRODUCTS + id, String.valueOf(userId));
        if (Boolean.FALSE.equals(success)) {
            log.warn("{} : {}", TAG, "获取锁失败");
            return ApiResult.error("获取锁失败");
        }
        try {
            String stockStr = redisTemplate.opsForValue().get(REDIS_KEY_STOCK_PRODUCTS + id);
            if (stockStr == null) {
                log.error("{} : {}", TAG, "库存信息缺失");
                return ApiResult.error("库存信息缺失");
            }
            int stock = Integer.parseInt(stockStr);
            if (stock <= 0) {
                log.error("{} : {}", TAG, "库存不足");
                return ApiResult.error("库存不足");
            }
            try {
                transactionTemplate.execute(status -> {
                    boolean updated = lambdaUpdate()
                            .setSql("stock = stock - 1")
                            .eq(ProductsEntity::getId, id)
                            .gt(ProductsEntity::getStock, 0)
                            .update();
                    if (!updated) {
                        log.error("{} : {}", TAG, "数据库库存扣减失败");
                        throw new RuntimeException("数据库库存扣减失败"); // 事务会回滚
                    }
                    return null;
                });
            } catch (RuntimeException e) {
                return ApiResult.error(e.getMessage());
            }
            redisTemplate.opsForValue().decrement(REDIS_KEY_STOCK_PRODUCTS + id);
            log.info("{} : {}", TAG, "扣减库存成功");
            return ApiResult.success("扣减库存成功");
        } finally {
            //问题：如果有别的用户误删了锁，可能导致锁提前释放，造成并发问题
            redisTemplate.delete(REDIS_KEY_LOCK_PRODUCTS + id);     // 释放锁
        }
    }

    /**
     * @Author: Alex
     * @Description: SE TNX EX 互斥锁，设置过期时间，避免死锁
     */
    @Override
    public ApiResult getProductWithSetNxEX(Integer id, Integer userId) {
        Boolean success = redisTemplate.opsForValue().setIfAbsent(REDIS_KEY_LOCK_PRODUCTS + id, String.valueOf(userId), 10, TimeUnit.SECONDS);
        if (Boolean.FALSE.equals(success)) {
            log.warn("{} : {}", TAG, "获取锁失败");
            return ApiResult.error("获取锁失败");
        }
        try {
            String stockStr = redisTemplate.opsForValue().get(REDIS_KEY_STOCK_PRODUCTS + id);
            if (stockStr == null) {
                log.error("{} : {}", TAG, "库存信息缺失");
                return ApiResult.error("库存信息缺失");
            }
            int stock = Integer.parseInt(stockStr);
            if (stock <= 0) {
                log.error("{} : {}", TAG, "库存不足");
                return ApiResult.error("库存不足");
            }
            try {
                transactionTemplate.execute(status -> {
                    boolean updated = lambdaUpdate()
                            .setSql("stock = stock - 1")
                            .eq(ProductsEntity::getId, id)
                            .gt(ProductsEntity::getStock, 0)
                            .update();
                    if (!updated) {
                        log.error("{} : {}", TAG, "数据库库存扣减失败");
                        throw new RuntimeException("数据库库存扣减失败"); // 事务会回滚
                    }
                    return null;
                });
            } catch (RuntimeException e) {
                return ApiResult.error(e.getMessage());
            }
            redisTemplate.opsForValue().decrement(REDIS_KEY_STOCK_PRODUCTS + id);
            log.info("{} : {}", TAG, "扣减库存成功");
            return ApiResult.success("扣减库存成功");
        } finally {
            //删除锁前检查 userId，避免误删其他线程加的锁，增强并发安全。
            //问题：手动检查 userId 再删除锁，可能存在并发问题
            String lockValue = redisTemplate.opsForValue().get(REDIS_KEY_LOCK_PRODUCTS + id);   // 释放锁
            if (lockValue != null && lockValue.equals(String.valueOf(userId))) {
                redisTemplate.delete(REDIS_KEY_LOCK_PRODUCTS + id);
            }
        }
    }

    /**
     * @Author: Alex
     * @Description: Lua脚本，保证原子操作
     */
    @Override
    public ApiResult purchaseWithLua(Integer id, Integer userId) {
        Boolean success = redisTemplate.opsForValue().setIfAbsent(REDIS_KEY_LOCK_PRODUCTS + id, String.valueOf(userId), 10, TimeUnit.SECONDS);
        if (Boolean.FALSE.equals(success)) {
            log.warn("{} : {}", TAG, "获取锁失败");
            return ApiResult.error("获取锁失败");
        }
        try {
            String stockStr = redisTemplate.opsForValue().get(REDIS_KEY_STOCK_PRODUCTS + id);
            if (stockStr == null) {
                log.error("{} : {}", TAG, "库存信息缺失");
                return ApiResult.error("库存信息缺失");
            }
            int stock = Integer.parseInt(stockStr);
            if (stock <= 0) {
                log.error("{} : {}", TAG, "库存不足");
                return ApiResult.error("库存不足");
            }
            try {
                transactionTemplate.execute(status -> {
                    boolean updated = lambdaUpdate()
                            .setSql("stock = stock - 1")
                            .eq(ProductsEntity::getId, id)
                            .gt(ProductsEntity::getStock, 0)
                            .update();
                    if (!updated) {
                        log.error("{} : {}", TAG, "数据库库存扣减失败");
                        throw new RuntimeException("数据库库存扣减失败"); // 事务会回滚
                    }
                    return null;
                });
            } catch (RuntimeException e) {
                return ApiResult.error(e.getMessage());
            }
            redisTemplate.opsForValue().decrement(REDIS_KEY_STOCK_PRODUCTS + id);
            log.info("{} : {}", TAG, "扣减库存成功");
            return ApiResult.success("扣减库存成功");
        } finally {
            //Lua 在 Redis 内部原子执行，确保只有当前 userId 持有锁时才删除
            String luaScript = "if redis.call(\"GET\", KEYS[1]) == ARGV[1] then return redis.call(\"DEL\", KEYS[1]) else return 0 end";
            RedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
            redisTemplate.execute(script, Collections.singletonList(REDIS_KEY_LOCK_PRODUCTS + id), String.valueOf(userId));
        }
    }

    /**
     * @Author: Alex
     * @Description: Redisson 分布式锁库，自动续期，防止死锁
     */
    @Override
    public ApiResult purchaseWithRedisson(Integer id, Integer userId) {
        RLock lock = redissonClient.getLock(REDIS_KEY_LOCK_PRODUCTS + id);
        try {
            // 尝试获取锁，最多等待 1 秒，如果获取到锁则使用 Redisson Watchdog 自动续期，避免锁过期导致死锁
            boolean locked = lock.tryLock(1, 0, TimeUnit.SECONDS);
            if (!locked) {
                log.warn("{} : {}", TAG, "获取锁失败");
                return ApiResult.error("获取锁失败");
            }
            String stockStr = redisTemplate.opsForValue().get(REDIS_KEY_STOCK_PRODUCTS + id);
            if (stockStr == null) {
                log.error("{} : {}", TAG, "库存信息缺失");
                return ApiResult.error("库存信息缺失");
            }
            int stock = Integer.parseInt(stockStr);
            if (stock <= 0) {
                log.error("{} : {}", TAG, "库存不足");
                return ApiResult.error("库存不足");
            }
            try {
                transactionTemplate.execute(status -> {
                    boolean updated = lambdaUpdate()
                            .setSql("stock = stock - 1")
                            .eq(ProductsEntity::getId, id)
                            .gt(ProductsEntity::getStock, 0)
                            .update();
                    if (!updated) {
                        log.error("{} : {}", TAG, "数据库库存扣减失败");
                        throw new RuntimeException("数据库库存扣减失败"); // 事务会回滚
                    }
                    return null;
                });
            } catch (RuntimeException e) {
                return ApiResult.error(e.getMessage());
            }
            redisTemplate.opsForValue().decrement(REDIS_KEY_STOCK_PRODUCTS + id);
            log.info("{} : {}", TAG, "扣减库存成功");
            return ApiResult.success("扣减库存成功");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 设置线程中断状态
            log.error("{} : {}", TAG, "出现异常");
            return ApiResult.error("出现异常" + e.getMessage());
        } finally {
            if (lock.isHeldByCurrentThread()) { // 确保只有当前线程释放锁
                lock.unlock();
            }
        }
    }

    /**
     * @ Author: Alex
     * @ Description: RabbitMQ 流量削峰
     */
    @Override
    public ApiResult purchaseWithRabbitMQ(Integer id, Integer userId) {
        // 使用 Redisson 获取锁，防止瞬时并发扣减冲突
        RLock lock = redissonClient.getLock(REDIS_KEY_LOCK_PRODUCTS + id);
        try {
            boolean locked = lock.tryLock(1, 10, TimeUnit.SECONDS);
            if (!locked) {
                return ApiResult.error("获取锁失败");
            }
            // 发送库存扣减请求到 MQ，异步处理
            rabbitTemplate.convertAndSend(
                    RabbitMQConfig.EXCHANGE_PRODUCT_ORDER,
                    RabbitMQConfig.ROUTING_KEY_PRODUCT_ORDER,
                    id
            );
            return ApiResult.success("请求已排队，稍后扣减库存");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return ApiResult.error("获取锁异常");
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}